; 本程序将完成如下功能
;   1、建立2个页目录表
;   2、将相同的线性地址映射到不同的物理地址页，使得相同的线性地址调用不同的函数
;   3、通过切换cr3寄存器，实现对同一个线性地址的调用显示不同的效果
jmp LABEL_BEGIN

%include "pm.inc"

PageDir1Base        equ     100000h             ; 页目录表1基地址，1M处，4K对齐
PageTable1Base      equ     100000h + 1000h     ; 页表1基地址，1M+4K处（1M~1M+4K之间为页表目录使用）
PageDir2Base        equ     200000h             ; 页目录2表基地址，2M处，4K对齐
PageTable2Base      equ     200000h + 1000h     ; 页表2基地址，2M+4K处（1M~1M+4K之间为页表目录使用）
PageTableSegLimit   equ     0400h               ; 页表段界限，4M = 4K * 1024, 颗粒度是4K，因此段界限是1024=0400h
SegBase             equ     10000h              ; 段基址，从64K处开始
SegLimit            equ     0800h               ; 段界限，8M = 4K * 2048, 颗粒度是4K，因此段界限是2048=0800h
SegVideoBase        equ     0B8000h             ; 显存段基址
TopOfStack          equ     0A000h              ; 栈顶（大地址）,举例段基址40K处，无特殊要求，避开上面的页表，代码即可

CodeStart_OFFSET_LA_3M     equ     0300000h - SegBase       ; 3M线性地址处的偏移
CodeStart_OFFSET_LA_5M     equ     0500000h - SegBase       ; 5M线性地址处的偏移

CodeEntry_Offset_LA_3M_AfterPaging      equ     0300000h - SegBase      ; 分页后的线性地址，同样的线性地址映射到不同的物理地址

[SECTION .gdt]
; GDT描述符
; 每一项的格式: 
;   数据段/代码段描述符：       段基址， 段界限，   属性
LABEL_GDT:                  Descriptor      0,              0,                          0                               ; 空描述符
LABEL_DESC_CODE:            Descriptor      SegBase,        SegLimit - 1,               DA_C + DA_32 + DA_DPL0 + DA_G_4K          ; 代码段
LABEL_DESC_DATA:            Descriptor      SegBase,        SegLimit - 1,               DA_DRW + DA_DPL0 + DA_G_4K                ; 数据段
LABEL_DESC_VIDEO:           Descriptor      SegVideoBase,   0ffffh,                     DA_DRW + DA_DPL0                          ; 显存地址段
LABEL_DESC_STACK:           Descriptor      SegBase,        TopOfStack - 1,             DA_DRWA + DA_32 + DA_DPL0 + DA_G_4K       ; 堆栈段                  

; 页目录和页表段描述符
LABEL_DESC_PDE1:             Descriptor      PageDir1Base,        01000h - 1,               DA_DRW + DA_DPL0                        ; 页目录表描述符
LABEL_DESC_PTE1:             Descriptor      PageTable1Base,      PageTableSegLimit - 1,    DA_DRW + DA_DPL0 + DA_G_4K              ; 页表描述符
LABEL_DESC_PDE2:             Descriptor      PageDir2Base,        01000h - 1,               DA_DRW + DA_DPL0                        ; 页目录表描述符
LABEL_DESC_PTE2:             Descriptor      PageTable2Base,      PageTableSegLimit - 1,    DA_DRW + DA_DPL0 + DA_G_4K              ; 页表描述符

; GDT结束

GdtLen equ $ - LABEL_GDT    ; GDT长度
GdtPtr dw GdtLen - 1        ; GDT界限
       dd 0                 ; GDT基地址

; GDT段选择符
SelectorCode            equ     LABEL_DESC_CODE - LABEL_GDT
SelectorData            equ     LABEL_DESC_DATA - LABEL_GDT
SelectorVideo           equ     LABEL_DESC_VIDEO - LABEL_GDT
SelectorStack           equ     LABEL_DESC_STACK - LABEL_GDT
SelectorPde1            equ     LABEL_DESC_PDE1 - LABEL_GDT
SelectorPte1            equ     LABEL_DESC_PTE1 - LABEL_GDT
SelectorPde2            equ     LABEL_DESC_PDE2 - LABEL_GDT
SelectorPte2            equ     LABEL_DESC_PTE2 - LABEL_GDT

; End of GDT SECTION

[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h ; 初始化各个段

    ; 在进入保护模式之前
    ; 完成段描述符的初始化

    ; 为加载GDTR做准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT  ; gdt基地址 ---> eax
    mov dword [GdtPtr + 2], eax ; gdt基地址 ---> [GdtPtr + 2]

    ; 加载GDTR
    lgdt [GdtPtr]

    ; 关中断 保护模式下的中断处理机制和实模式下不一样，先简单关中断处理
    cli

    ; 打开地址线A20，历史原因，打开它才能访问超过1M的地址空间
    in al, 92h
    or al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    ; 开启分段
    mov eax, cr0
    or eax, 1       ; CR0的最低位是PE，该值为1时表示启动分段保护模式，开启PE=1模式后，必须立马使用跳转命令，例如下面的jmp命令
    mov cr0, eax

    jmp dword SelectorCode:LABEL_SEG_CODE_ENTRY

; End of [SECTION .s16]


EnterProtectMode: db "Welcome to protect mode!"
EnterProtectModeLen equ $ - EnterProtectMode

CopyCode: db "Copy code finished!"
CopyCodeLen equ $ - CopyCode

PageInit: db "Page init finished!"
PageInitLen equ $ - PageInit

[SECTION .code32]
[BITS 32]
LABEL_SEG_CODE_ENTRY:
    mov ax, SelectorData
    mov ds, ax
    mov es, ax
    mov ax, SelectorVideo
    mov gs, ax
    mov ax, SelectorStack
    mov ss, ax
    mov esp, TopOfStack

    mov edi, (80 * 0 + 0) * 2

    call CleanScreen

    mov eax, EnterProtectMode
    mov ecx, EnterProtectModeLen
    call DispStr

    push edi
    push esi

    ; 将代码拷贝到物理地址3M处，此时还未分页，物理地址和线性地址相同
    mov edi, CodeStart_OFFSET_LA_3M
    mov ecx, CodeForPa3MLen
    mov esi, LABEL_SEG_CODE_FOR_PA_3M
    mov edx, ecx
    call MemCpy

    ; 将代码拷贝到物理地址5M处，此时还未分页，物理地址和线性地址相同
    mov edi, CodeStart_OFFSET_LA_5M
    mov ecx, CodeForPa5MLen
    mov esi, LABEL_SEG_CODE_FOR_PA_5M
    mov edx, ecx
    call MemCpy

    pop esi
    pop edi

    call DispReturn

    mov eax, CopyCode
    mov ecx, CopyCodeLen
    call DispStr

    push es
    push edi
    push esi
    ; 此处进行分页的初始化

    ; 页目录1和页表1的初始化：只用到线性地址4M的空间，只需要1个页表
    mov ax, SelectorPde1
    mov es, ax
    xor edi, edi
    mov eax, PageTable1Base
    add eax, PA_P + PA_RW
    stosd                   ; 页目录表项1
    add eax, 01000h
    stosd                   ; 页目录表项2

    ; 页表1的初始化，0-4M线性地址和物理地址一致，也就是[0,4M]-->[0,4M]
    mov ax, SelectorPte1
    mov es, ax
    xor edi, edi
    mov eax, 0              ; 物理页从地址0开始
    add eax, PA_P + PA_RW
    mov ecx, 1024           ; 1个页表有1024项
.loop_tbl_1:
    stosd
    add eax, 01000h
    loop .loop_tbl_1    


    ; 页目录2和页表2的初始化:：只用到线性地址4M的空间，只需要1个页表
    mov ax, SelectorPde2
    mov es, ax
    xor edi, edi
    mov eax, PageTable2Base
    add eax, PA_P + PA_RW
    stosd                   ; 页目录表项1
    add eax, 01000h
    stosd                   ; 页目录表项2
    
    ; 页表2的初始化，0-3M线性地址和物理地址一致，3-4M映射到物理地址5-6M ，也就是[0,3M]-->[0,3M]，[3,4M]-->[5,6M]
    mov ax, SelectorPte2
    mov es, ax
    xor edi, edi
    mov eax, 0              ; 物理页从地址0开始
    add eax, PA_P + PA_RW
    mov ecx, 0300h          ; 1个页表有1024项,先映射前面3M的,也就是0300h个页表项
.loop_tbl_2_3M:
    stosd
    add eax, 01000h
    loop .loop_tbl_2_3M

    mov ecx, 0100h          ; 后面0100h=256个页表项，要映射到地址5-6M，运行到此处时候，第0300h个页表的物理基地址是0300000h
    add eax, 0200000h       ; 物理地址在0300000h基础上加0200000h，这样，从下一个页表项开始，映射物理地址从0500000h开始，此时的线性地址是0300000h
.loop_tbl_2_1M:
    stosd
    add eax, 01000h
    loop .loop_tbl_2_1M

    pop esi
    pop edi
    pop es

    ; 至此，已经完成页初始化
    call DispReturn
    mov eax, PageInit
    mov ecx, PageInitLen
    call DispStr


    ; 接下来要开启分页功能
    mov eax, PageDir1Base               ; 页目录1基地址
    mov cr3, eax                        ; 将页目录表基地址(物理地址)存起来
    mov eax, cr0
    or  eax, 80000000h                  ; 开启分页，PG=1
    mov cr0, eax

    ; 当前处理页表目录1映射空间，尝试对线性地址偏移CodeEntry_Offset_LA_3M_AfterPaging进行调用
    call DispReturn
    call CodeEntry_Offset_LA_3M_AfterPaging

    ; 然后切换一下页目录表空间到页目录表2
    mov eax, PageDir2Base               ; 页目录2基地址
    mov cr3, eax

    ; 当前处理页表目录2映射空间，尝试对线性地址偏移CodeEntry_Offset_LA_3M_AfterPaging进行调用
    call DispReturn
    call CodeEntry_Offset_LA_3M_AfterPaging 

    jmp $

; End of SECTION .code32 cpl0

ShowStringForPA3M: db "run testA..."
ShowStringForPA3MLen equ $ - ShowStringForPA3M
; 此段代码在分页前拷贝到3M物理地址段处
; 注意，由于要拷贝到内存的其他地方，因此函数里面不能有基于当前位置的偏移的计算，例如使用call再次调用其它函数
LABEL_SEG_CODE_FOR_PA_3M:
    mov ecx, ShowStringForPA3MLen
    push esi
    mov esi, ShowStringForPA3M
    mov ah, 0ch     ; 0000 黑底， 000C 红字
.loop:
    mov al, byte [ds:esi]
    mov [gs:edi], ax
    add edi, 2
    inc esi
    loop .loop
    pop esi
    ret
CodeForPa3MLen equ $ - LABEL_SEG_CODE_FOR_PA_3M


ShowStringForPA5M: db "run testB..."
ShowStringForPA5MLen equ $ - ShowStringForPA5M
; 此段代码在分页前拷贝到5M物理地址段处
; 注意，由于要拷贝到内存的其他地方，因此函数里面不能有基于当前位置的偏移的计算，例如使用call再次调用其它函数
LABEL_SEG_CODE_FOR_PA_5M:
    mov ecx, ShowStringForPA5MLen
    push esi
    mov esi, ShowStringForPA5M
    mov ah, 0ch     ; 0000 黑底， 000C 红字
.loop_1:
    mov al, byte [ds:esi]
    mov [gs:edi], ax
    add edi, 2
    inc esi
    loop .loop_1
    pop esi
    ret
CodeForPa5MLen equ $ - LABEL_SEG_CODE_FOR_PA_5M



%include "lib.inc"